{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "3b8465c3-32e4-4d6c-9476-e75571d1fc1e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G1_N30_L_L1_D0_20200408_2_Labeled.csv\n",
      "[0 1]\n",
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G2_N30_L_L1_D0_20200408_2_Labeled.csv\n",
      "[0 2]\n",
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G3_N30_L_L1_D0_20200408_2_Labeled.csv\n",
      "[0 3]\n",
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G1_N10_L_L1_D0_20200408_1_Labeled.csv\n",
      "[0 1]\n",
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G2_N10_L_L1_D0_20200408_1_Labeled.csv\n",
      "[0 2]\n",
      "D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G3_N10_L_L1_D0_20200408_1_Labeled.csv\n",
      "[0 3]\n",
      "Training segments: 183\n",
      "Training Segment 1: 6414\n",
      "Training Segment 2: 3504\n",
      "Training Segment 3: 1214\n",
      "Training Segment 4: 2960\n",
      "Training Segment 5: 974\n",
      "Training Segment 6: 3024\n",
      "Training Segment 7: 814\n",
      "Training Segment 8: 3248\n",
      "Training Segment 9: 734\n",
      "Training Segment 10: 2928\n",
      "Training Segment 11: 782\n",
      "Training Segment 12: 2480\n",
      "Training Segment 13: 1102\n",
      "Training Segment 14: 2656\n",
      "Training Segment 15: 750\n",
      "Training Segment 16: 2672\n",
      "Training Segment 17: 606\n",
      "Training Segment 18: 3200\n",
      "Training Segment 19: 350\n",
      "Training Segment 20: 3392\n",
      "Training Segment 21: 878\n",
      "Training Segment 22: 3136\n",
      "Training Segment 23: 686\n",
      "Training Segment 24: 3408\n",
      "Training Segment 25: 558\n",
      "Training Segment 26: 3056\n",
      "Training Segment 27: 798\n",
      "Training Segment 28: 3472\n",
      "Training Segment 29: 670\n",
      "Training Segment 30: 3328\n",
      "Training Segment 31: 446\n",
      "Training Segment 32: 3472\n",
      "Training Segment 33: 798\n",
      "Training Segment 34: 2960\n",
      "Training Segment 35: 1438\n",
      "Training Segment 36: 3184\n",
      "Training Segment 37: 1134\n",
      "Training Segment 38: 2736\n",
      "Training Segment 39: 718\n",
      "Training Segment 40: 3200\n",
      "Training Segment 41: 942\n",
      "Training Segment 42: 3168\n",
      "Training Segment 43: 1454\n",
      "Training Segment 44: 2848\n",
      "Training Segment 45: 1342\n",
      "Training Segment 46: 3168\n",
      "Training Segment 47: 910\n",
      "Training Segment 48: 2624\n",
      "Training Segment 49: 1198\n",
      "Training Segment 50: 3024\n",
      "Training Segment 51: 974\n",
      "Training Segment 52: 3088\n",
      "Training Segment 53: 974\n",
      "Training Segment 54: 3200\n",
      "Training Segment 55: 558\n",
      "Training Segment 56: 3408\n",
      "Training Segment 57: 798\n",
      "Training Segment 58: 2928\n",
      "Training Segment 59: 1038\n",
      "Training Segment 60: 3424\n",
      "Training Segment 61: 7663\n",
      "Training Segment 62: 7870\n",
      "Training Segment 63: 3632\n",
      "Training Segment 64: 1502\n",
      "Training Segment 65: 4240\n",
      "Training Segment 66: 1102\n",
      "Training Segment 67: 3392\n",
      "Training Segment 68: 1582\n",
      "Training Segment 69: 4096\n",
      "Training Segment 70: 1822\n",
      "Training Segment 71: 3520\n",
      "Training Segment 72: 1470\n",
      "Training Segment 73: 3936\n",
      "Training Segment 74: 1118\n",
      "Training Segment 75: 4480\n",
      "Training Segment 76: 1246\n",
      "Training Segment 77: 3552\n",
      "Training Segment 78: 1646\n",
      "Training Segment 79: 3824\n",
      "Training Segment 80: 2094\n",
      "Training Segment 81: 3440\n",
      "Training Segment 82: 2270\n",
      "Training Segment 83: 4160\n",
      "Training Segment 84: 1630\n",
      "Training Segment 85: 4096\n",
      "Training Segment 86: 1630\n",
      "Training Segment 87: 4256\n",
      "Training Segment 88: 1774\n",
      "Training Segment 89: 3472\n",
      "Training Segment 90: 2270\n",
      "Training Segment 91: 3600\n",
      "Training Segment 92: 2110\n",
      "Training Segment 93: 3696\n",
      "Training Segment 94: 2190\n",
      "Training Segment 95: 3888\n",
      "Training Segment 96: 1902\n",
      "Training Segment 97: 4208\n",
      "Training Segment 98: 1886\n",
      "Training Segment 99: 4256\n",
      "Training Segment 100: 2030\n",
      "Training Segment 101: 4048\n",
      "Training Segment 102: 2478\n",
      "Training Segment 103: 4000\n",
      "Training Segment 104: 2238\n",
      "Training Segment 105: 4224\n",
      "Training Segment 106: 3022\n",
      "Training Segment 107: 4480\n",
      "Training Segment 108: 1310\n",
      "Training Segment 109: 4208\n",
      "Training Segment 110: 2174\n",
      "Training Segment 111: 4096\n",
      "Training Segment 112: 2238\n",
      "Training Segment 113: 4144\n",
      "Training Segment 114: 2766\n",
      "Training Segment 115: 4832\n",
      "Training Segment 116: 1454\n",
      "Training Segment 117: 4224\n",
      "Training Segment 118: 4526\n",
      "Training Segment 119: 4336\n",
      "Training Segment 120: 3438\n",
      "Training Segment 121: 4464\n",
      "Training Segment 122: 9807\n",
      "Training Segment 123: 17694\n",
      "Training Segment 124: 2512\n",
      "Training Segment 125: 1758\n",
      "Training Segment 126: 2192\n",
      "Training Segment 127: 1150\n",
      "Training Segment 128: 2592\n",
      "Training Segment 129: 1694\n",
      "Training Segment 130: 2176\n",
      "Training Segment 131: 1198\n",
      "Training Segment 132: 2144\n",
      "Training Segment 133: 1710\n",
      "Training Segment 134: 2032\n",
      "Training Segment 135: 2222\n",
      "Training Segment 136: 2256\n",
      "Training Segment 137: 2110\n",
      "Training Segment 138: 1760\n",
      "Training Segment 139: 2718\n",
      "Training Segment 140: 2176\n",
      "Training Segment 141: 2110\n",
      "Training Segment 142: 2112\n",
      "Training Segment 143: 2798\n",
      "Training Segment 144: 2400\n",
      "Training Segment 145: 2558\n",
      "Training Segment 146: 2208\n",
      "Training Segment 147: 2926\n",
      "Training Segment 148: 2496\n",
      "Training Segment 149: 2590\n",
      "Training Segment 150: 2448\n",
      "Training Segment 151: 2526\n",
      "Training Segment 152: 2416\n",
      "Training Segment 153: 3006\n",
      "Training Segment 154: 2320\n",
      "Training Segment 155: 3518\n",
      "Training Segment 156: 2032\n",
      "Training Segment 157: 5406\n",
      "Training Segment 158: 2240\n",
      "Training Segment 159: 5278\n",
      "Training Segment 160: 2304\n",
      "Training Segment 161: 8094\n",
      "Training Segment 162: 2192\n",
      "Training Segment 163: 22366\n",
      "Training Segment 164: 2352\n",
      "Training Segment 165: 1726\n",
      "Training Segment 166: 2768\n",
      "Training Segment 167: 2430\n",
      "Training Segment 168: 2176\n",
      "Training Segment 169: 3166\n",
      "Training Segment 170: 2208\n",
      "Training Segment 171: 3950\n",
      "Training Segment 172: 3360\n",
      "Training Segment 173: 3278\n",
      "Training Segment 174: 2672\n",
      "Training Segment 175: 2558\n",
      "Training Segment 176: 2400\n",
      "Training Segment 177: 3630\n",
      "Training Segment 178: 1712\n",
      "Training Segment 179: 3838\n",
      "Training Segment 180: 2240\n",
      "Training Segment 181: 2606\n",
      "Training Segment 182: 2416\n",
      "Training Segment 183: 8559\n",
      "Testing segments: 63\n"
     ]
    }
   ],
   "source": [
    "import pandas as pd\n",
    "\n",
    "# Define training and testing files\n",
    "training_files = [\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G1_N30_L_L1_D0_20200408_2_Labeled.csv',\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G2_N30_L_L1_D0_20200408_2_Labeled.csv',\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G3_N30_L_L1_D0_20200408_2_Labeled.csv']\n",
    "\n",
    "testing_files = [\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G1_N10_L_L1_D0_20200408_1_Labeled.csv',\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G2_N10_L_L1_D0_20200408_1_Labeled.csv',\n",
    "    r'D:\\BaiduNetdiskDownload\\HBPU-Machine-Learning-Course\\U1_G3_N10_L_L1_D0_20200408_1_Labeled.csv'\n",
    "]\n",
    "\n",
    "# Function to read and process CSV files\n",
    "def read_csv_file(file_path):\n",
    "    print(file_path)\n",
    "    df = pd.read_csv(file_path)\n",
    "    csi = df.iloc[:, 2:].values  # All columns except 'timestamp' and 'label'\n",
    "    label = df['label'].values # 0: static, 1: up, 2: down, 3: left, 4: right\n",
    "    timestamp = df['timestamp'].values\n",
    "    print(np.unique(label))\n",
    "    return csi, label, timestamp\n",
    "\n",
    "def segment_signals(csi, label, timestamp):\n",
    "    segments = [] # Store segments\n",
    "    segment_label = label[0] # Initialize segment label\n",
    "    segment_start = 0 # Initialize segment start index\n",
    "\n",
    "    for i in range(len(label)): # Iterate through all labels\n",
    "        if label[i] != segment_label: # If the label is different from the current segment label\n",
    "            segments.append((csi[segment_start:i-1], segment_label, timestamp[segment_start:i-1])) # Append the current segment to the segments list\n",
    "            segment_start = i # Update the segment start index\n",
    "            segment_label = label[i] # Update the segment label\n",
    "\n",
    "    segments.append((csi[segment_start:], segment_label, timestamp[segment_start:])) # Append the last segment to the segments list\n",
    "    return segments\n",
    "\n",
    "# Define training and testing segments\n",
    "training_segments = []\n",
    "testing_segments = []\n",
    "\n",
    "# Read and process training files\n",
    "for file in training_files:\n",
    "    s, y, t = read_csv_file(file)\n",
    "    training_segments.extend(segment_signals(s, y, t))\n",
    "\n",
    "# Read and process testing files\n",
    "for file in testing_files:\n",
    "    s, y, t = read_csv_file(file)\n",
    "    testing_segments.extend(segment_signals(s, y, t))\n",
    "\n",
    "# Print sizes of the training segments and testing segments\n",
    "print(f\"Training segments: {len(training_segments)}\")\n",
    "\n",
    "# Print length of all training segments\n",
    "for i, (s, y, t) in enumerate(training_segments):\n",
    "    print(f\"Training Segment {i + 1}: {len(s)}\")\n",
    "\n",
    "# Print size of the testing segments\n",
    "print(f\"Testing segments: {len(testing_segments)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "2c3dead7-4e51-40ae-9921-f557147a40a0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from scipy.stats import kurtosis\n",
    "from scipy.stats import skew\n",
    "\n",
    "# Extract features of training segments\n",
    "def extract_features(s):\n",
    "    ''' \n",
    "    Extract features of each segment\n",
    "    features include:\n",
    "    - mean\n",
    "    - std\n",
    "    - max\n",
    "    - min\n",
    "    - median\n",
    "    - kurtosis\n",
    "    - skew\n",
    "    \n",
    "    Input:\n",
    "    s: segment (N*30) in training_segments or testing_segments\n",
    "    \n",
    "    Output:\n",
    "    x: 1-D vector (8*30)\n",
    "    '''\n",
    "    x = []\n",
    "    x.extend(np.mean(s, axis=0))\n",
    "    x.extend(np.std(s, axis=0))\n",
    "    x.extend(np.max(s, axis=0))\n",
    "    x.extend(np.min(s, axis=0))\n",
    "    x.extend(np.median(s, axis=0))\n",
    "    x.extend(kurtosis(s, axis=0))\n",
    "    x.extend(skew(s, axis=0))\n",
    "\n",
    "    return np.array(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "edc096d6-4ddc-4cbf-a906-b04e1a86953c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([[1., 2., 3.],\n",
      "        [4., 5., 6.],\n",
      "        [7., 8., 9.]])\n"
     ]
    }
   ],
   "source": [
    "import torch\n",
    "from torch.utils.data import DataLoader, TensorDataset\n",
    "import numpy as np\n",
    "import torch\n",
    "\n",
    "# 假设 trX 是一个包含多个numpy数组的列表\n",
    "trX = [np.array([1, 2, 3]), np.array([4, 5, 6]), np.array([7, 8, 9])]\n",
    "\n",
    "# 将列表转换为一个单一的numpy数组\n",
    "trX_np = np.array(trX, dtype=np.float32)  # 指定数据类型为float32\n",
    "\n",
    "# 将numpy数组转换为PyTorch张量\n",
    "trX = torch.tensor(trX_np, dtype=torch.float32)  # 转换为张量\n",
    "\n",
    "print(trX)\n",
    "def one_hot_collate(batch):\n",
    "    data = torch.stack([item[0] for item in batch])\n",
    "    labels = torch.tensor([item[1] for item in batch])\n",
    "    \n",
    "    one_hot_labels = torch.zeros(labels.size(0), 4)  # 4 classes\n",
    "    one_hot_labels.scatter_(1, labels.unsqueeze(1), 1)\n",
    "    return data, one_hot_labels\n",
    "\n",
    "batch_size = 4\n",
    "\n",
    "# Build training dataset\n",
    "trX = [extract_features(s) for s, _, _ in training_segments] # Extract features of training segments\n",
    "trX = torch.tensor(trX, dtype=torch.float32) # Convert trX to tensor\n",
    "trY = [y for _, y, _ in training_segments] # Extract labels of training segments\n",
    "trY = torch.tensor(trY) # Convert trY to tensor\n",
    "\n",
    "# Build testing dataset\n",
    "teX = [extract_features(s) for s, _, _ in testing_segments] # Extract features of testing segments\n",
    "teX = torch.tensor(teX, dtype=torch.float32) # Convert teX to tensor\n",
    "teY = [y for _, y, _ in testing_segments] # Extract labels of testing segments\n",
    "teY = torch.tensor(teY) # Convert teY to tensor\n",
    "\n",
    "# Normalize trX and teX\n",
    "# Calculate mean and standard deviation from the training data\n",
    "mean = trX.mean(dim=0)\n",
    "std = trX.std(dim=0)\n",
    "\n",
    "# Normalize training data\n",
    "trX = (trX - mean) / std\n",
    "\n",
    "# Normalize testing data using training mean and std\n",
    "teX = (teX - mean) / std\n",
    "\n",
    "# Build Dataset\n",
    "trDataset = TensorDataset(trX, trY) # Create training dataset\n",
    "teDataset = TensorDataset(teX, teY) # Create testing dataset\n",
    "\n",
    "# Build loader\n",
    "trLoader = DataLoader(trDataset, batch_size=batch_size, shuffle=True, num_workers=0, collate_fn=one_hot_collate) # Create training dataloader\n",
    "teLoader = DataLoader(teDataset, batch_size=batch_size, shuffle=False, num_workers=0, collate_fn=one_hot_collate) # Create testing dataloader"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "66840bbc-ff5f-4328-8643-f3938d839fff",
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch.nn as nn\n",
    "\n",
    "class FNN(nn.Module):\n",
    "    def __init__(self, input_size, hidden_size, num_classes):\n",
    "        super(FNN, self).__init__()\n",
    "        self.fc1 = nn.Linear(input_size, hidden_size)\n",
    "        self.relu1 = nn.ReLU()\n",
    "        self.fc2 = nn.Linear(hidden_size, hidden_size)\n",
    "        self.relu2 = nn.ReLU()\n",
    "        self.fc3 = nn.Linear(hidden_size, num_classes)\n",
    "        self.softmax = nn.Softmax(dim=1)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        x = self.fc1(x)\n",
    "        x = self.relu1(x)\n",
    "        x = self.fc2(x)\n",
    "        x = self.relu2(x)\n",
    "        x = self.fc3(x)\n",
    "        out = self.softmax(x)\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e5824e99-2936-4bc7-9289-a9203f3e9485",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "FNN(\n",
      "  (fc1): Linear(in_features=210, out_features=10, bias=True)\n",
      "  (relu1): ReLU()\n",
      "  (fc2): Linear(in_features=10, out_features=10, bias=True)\n",
      "  (relu2): ReLU()\n",
      "  (fc3): Linear(in_features=10, out_features=4, bias=True)\n",
      "  (softmax): Softmax(dim=1)\n",
      ")\n",
      "Epoch [1/200], Train Loss: 1.3209, CV Loss: 1.2732\n",
      "Epoch [2/200], Train Loss: 1.1878, CV Loss: 1.1529\n",
      "Epoch [3/200], Train Loss: 1.0786, CV Loss: 1.1068\n",
      "Epoch [4/200], Train Loss: 1.0066, CV Loss: 1.0986\n",
      "Epoch [5/200], Train Loss: 0.9710, CV Loss: 1.0915\n",
      "Epoch [6/200], Train Loss: 0.9492, CV Loss: 1.0854\n",
      "Epoch [7/200], Train Loss: 0.9358, CV Loss: 1.0784\n",
      "Epoch [8/200], Train Loss: 0.9269, CV Loss: 1.0734\n",
      "Epoch [9/200], Train Loss: 0.9187, CV Loss: 1.0701\n",
      "Epoch [10/200], Train Loss: 0.9127, CV Loss: 1.0697\n",
      "Epoch [11/200], Train Loss: 0.9063, CV Loss: 1.0658\n",
      "Epoch [12/200], Train Loss: 0.8996, CV Loss: 1.0627\n",
      "Epoch [13/200], Train Loss: 0.8949, CV Loss: 1.0595\n",
      "Epoch [14/200], Train Loss: 0.8880, CV Loss: 1.0572\n",
      "Epoch [15/200], Train Loss: 0.8813, CV Loss: 1.0538\n",
      "Epoch [16/200], Train Loss: 0.8744, CV Loss: 1.0477\n",
      "Epoch [17/200], Train Loss: 0.8723, CV Loss: 1.0488\n",
      "Epoch [18/200], Train Loss: 0.8605, CV Loss: 1.0407\n",
      "Epoch [19/200], Train Loss: 0.8526, CV Loss: 1.0390\n",
      "Epoch [20/200], Train Loss: 0.8446, CV Loss: 1.0368\n",
      "Epoch [21/200], Train Loss: 0.8390, CV Loss: 1.0385\n",
      "Epoch [22/200], Train Loss: 0.8290, CV Loss: 1.0349\n",
      "Epoch [23/200], Train Loss: 0.8251, CV Loss: 1.0361\n",
      "Epoch [24/200], Train Loss: 0.8215, CV Loss: 1.0401\n",
      "Epoch [25/200], Train Loss: 0.8160, CV Loss: 1.0328\n",
      "Epoch [26/200], Train Loss: 0.8141, CV Loss: 1.0319\n",
      "Epoch [27/200], Train Loss: 0.8075, CV Loss: 1.0299\n",
      "Epoch [28/200], Train Loss: 0.8015, CV Loss: 1.0300\n",
      "Epoch [29/200], Train Loss: 0.7976, CV Loss: 1.0302\n",
      "Epoch [30/200], Train Loss: 0.7936, CV Loss: 1.0303\n",
      "Epoch [31/200], Train Loss: 0.7904, CV Loss: 1.0327\n",
      "Epoch [32/200], Train Loss: 0.7848, CV Loss: 1.0329\n",
      "Epoch [33/200], Train Loss: 0.7857, CV Loss: 1.0357\n",
      "Epoch [34/200], Train Loss: 0.7766, CV Loss: 1.0353\n",
      "Epoch [35/200], Train Loss: 0.7780, CV Loss: 1.0356\n",
      "Epoch [36/200], Train Loss: 0.7734, CV Loss: 1.0350\n",
      "Epoch [37/200], Train Loss: 0.7724, CV Loss: 1.0374\n",
      "Epoch [38/200], Train Loss: 0.7699, CV Loss: 1.0334\n",
      "Epoch [39/200], Train Loss: 0.7695, CV Loss: 1.0352\n",
      "Epoch [40/200], Train Loss: 0.7672, CV Loss: 1.0341\n",
      "Epoch [41/200], Train Loss: 0.7659, CV Loss: 1.0335\n",
      "Epoch [42/200], Train Loss: 0.7640, CV Loss: 1.0321\n",
      "Epoch [43/200], Train Loss: 0.7632, CV Loss: 1.0355\n",
      "Epoch [44/200], Train Loss: 0.7630, CV Loss: 1.0326\n",
      "Epoch [45/200], Train Loss: 0.7617, CV Loss: 1.0326\n",
      "Epoch [46/200], Train Loss: 0.7609, CV Loss: 1.0319\n",
      "Epoch [47/200], Train Loss: 0.7604, CV Loss: 1.0312\n",
      "Epoch [48/200], Train Loss: 0.7604, CV Loss: 1.0317\n",
      "Epoch [49/200], Train Loss: 0.7593, CV Loss: 1.0301\n",
      "Epoch [50/200], Train Loss: 0.7589, CV Loss: 1.0300\n",
      "Epoch [51/200], Train Loss: 0.7586, CV Loss: 1.0284\n",
      "Epoch [52/200], Train Loss: 0.7581, CV Loss: 1.0288\n",
      "Epoch [53/200], Train Loss: 0.7579, CV Loss: 1.0273\n",
      "Epoch [54/200], Train Loss: 0.7577, CV Loss: 1.0277\n",
      "Epoch [55/200], Train Loss: 0.7574, CV Loss: 1.0309\n",
      "Epoch [56/200], Train Loss: 0.7572, CV Loss: 1.0285\n",
      "Epoch [57/200], Train Loss: 0.7569, CV Loss: 1.0278\n",
      "Epoch [58/200], Train Loss: 0.7567, CV Loss: 1.0282\n",
      "Epoch [59/200], Train Loss: 0.7564, CV Loss: 1.0277\n",
      "Epoch [60/200], Train Loss: 0.7563, CV Loss: 1.0300\n",
      "Epoch [61/200], Train Loss: 0.7561, CV Loss: 1.0285\n",
      "Epoch [62/200], Train Loss: 0.7559, CV Loss: 1.0291\n",
      "Epoch [63/200], Train Loss: 0.7558, CV Loss: 1.0288\n",
      "Epoch [64/200], Train Loss: 0.7557, CV Loss: 1.0282\n",
      "Epoch [65/200], Train Loss: 0.7572, CV Loss: 1.0281\n",
      "Epoch [66/200], Train Loss: 0.7555, CV Loss: 1.0281\n",
      "Epoch [67/200], Train Loss: 0.7554, CV Loss: 1.0299\n",
      "Epoch [68/200], Train Loss: 0.7552, CV Loss: 1.0304\n",
      "Epoch [69/200], Train Loss: 0.7551, CV Loss: 1.0314\n",
      "Epoch [70/200], Train Loss: 0.7551, CV Loss: 1.0292\n",
      "Epoch [71/200], Train Loss: 0.7550, CV Loss: 1.0300\n",
      "Epoch [72/200], Train Loss: 0.7549, CV Loss: 1.0294\n",
      "Epoch [73/200], Train Loss: 0.7549, CV Loss: 1.0319\n",
      "Epoch [74/200], Train Loss: 0.7548, CV Loss: 1.0303\n",
      "Epoch [75/200], Train Loss: 0.7547, CV Loss: 1.0302\n",
      "Epoch [76/200], Train Loss: 0.7546, CV Loss: 1.0300\n",
      "Epoch [77/200], Train Loss: 0.7545, CV Loss: 1.0305\n",
      "Epoch [78/200], Train Loss: 0.7545, CV Loss: 1.0301\n",
      "Epoch [79/200], Train Loss: 0.7545, CV Loss: 1.0306\n",
      "Epoch [80/200], Train Loss: 0.7544, CV Loss: 1.0303\n",
      "Epoch [81/200], Train Loss: 0.7543, CV Loss: 1.0298\n",
      "Epoch [82/200], Train Loss: 0.7543, CV Loss: 1.0296\n",
      "Epoch [83/200], Train Loss: 0.7543, CV Loss: 1.0273\n",
      "Epoch [84/200], Train Loss: 0.7543, CV Loss: 1.0303\n",
      "Epoch [85/200], Train Loss: 0.7544, CV Loss: 1.0359\n",
      "Epoch [86/200], Train Loss: 0.7544, CV Loss: 1.0286\n",
      "Epoch [87/200], Train Loss: 0.7543, CV Loss: 1.0313\n",
      "Epoch [88/200], Train Loss: 0.7542, CV Loss: 1.0360\n",
      "Epoch [89/200], Train Loss: 0.7539, CV Loss: 1.0343\n",
      "Epoch [90/200], Train Loss: 0.7539, CV Loss: 1.0351\n",
      "Epoch [91/200], Train Loss: 0.7538, CV Loss: 1.0398\n",
      "Epoch [92/200], Train Loss: 0.7535, CV Loss: 1.0475\n",
      "Epoch [93/200], Train Loss: 0.7531, CV Loss: 1.0580\n",
      "Epoch [94/200], Train Loss: 0.7516, CV Loss: 1.0470\n",
      "Epoch [95/200], Train Loss: 0.7500, CV Loss: 1.0591\n",
      "Epoch [96/200], Train Loss: 0.7496, CV Loss: 1.0533\n",
      "Epoch [97/200], Train Loss: 0.7493, CV Loss: 1.0561\n",
      "Epoch [98/200], Train Loss: 0.7494, CV Loss: 1.0551\n",
      "Epoch [99/200], Train Loss: 0.7493, CV Loss: 1.0554\n",
      "Epoch [100/200], Train Loss: 0.7493, CV Loss: 1.0548\n",
      "Epoch [101/200], Train Loss: 0.7491, CV Loss: 1.0547\n",
      "Epoch [102/200], Train Loss: 0.7492, CV Loss: 1.0541\n",
      "Epoch [103/200], Train Loss: 0.7492, CV Loss: 1.0555\n",
      "Epoch [104/200], Train Loss: 0.7492, CV Loss: 1.0535\n",
      "Epoch [105/200], Train Loss: 0.7491, CV Loss: 1.0550\n",
      "Epoch [106/200], Train Loss: 0.7490, CV Loss: 1.0540\n",
      "Epoch [107/200], Train Loss: 0.7490, CV Loss: 1.0533\n",
      "Epoch [108/200], Train Loss: 0.7490, CV Loss: 1.0534\n",
      "Epoch [109/200], Train Loss: 0.7489, CV Loss: 1.0538\n",
      "Epoch [110/200], Train Loss: 0.7489, CV Loss: 1.0544\n",
      "Epoch [111/200], Train Loss: 0.7488, CV Loss: 1.0530\n",
      "Epoch [112/200], Train Loss: 0.7489, CV Loss: 1.0541\n",
      "Epoch [113/200], Train Loss: 0.7488, CV Loss: 1.0532\n",
      "Epoch [114/200], Train Loss: 0.7488, CV Loss: 1.0535\n",
      "Epoch [115/200], Train Loss: 0.7488, CV Loss: 1.0538\n",
      "Epoch [116/200], Train Loss: 0.7490, CV Loss: 1.0532\n",
      "Epoch [117/200], Train Loss: 0.7489, CV Loss: 1.0535\n",
      "Epoch [118/200], Train Loss: 0.7489, CV Loss: 1.0531\n",
      "Epoch [119/200], Train Loss: 0.7490, CV Loss: 1.0547\n",
      "Epoch [120/200], Train Loss: 0.7491, CV Loss: 1.0533\n",
      "Epoch [121/200], Train Loss: 0.7488, CV Loss: 1.0542\n",
      "Epoch [122/200], Train Loss: 0.7488, CV Loss: 1.0534\n",
      "Epoch [123/200], Train Loss: 0.7487, CV Loss: 1.0537\n",
      "Epoch [124/200], Train Loss: 0.7487, CV Loss: 1.0535\n",
      "Epoch [125/200], Train Loss: 0.7487, CV Loss: 1.0529\n",
      "Epoch [126/200], Train Loss: 0.7488, CV Loss: 1.0534\n",
      "Epoch [127/200], Train Loss: 0.7489, CV Loss: 1.0550\n",
      "Epoch [128/200], Train Loss: 0.7490, CV Loss: 1.0524\n",
      "Epoch [129/200], Train Loss: 0.7446, CV Loss: 1.0551\n",
      "Epoch [130/200], Train Loss: 0.7450, CV Loss: 1.0393\n",
      "Epoch [131/200], Train Loss: 0.7496, CV Loss: 1.0235\n",
      "Epoch [132/200], Train Loss: 0.7481, CV Loss: 1.0412\n",
      "Epoch [133/200], Train Loss: 0.7487, CV Loss: 1.0430\n",
      "Epoch [134/200], Train Loss: 0.7439, CV Loss: 1.0445\n",
      "Epoch [135/200], Train Loss: 0.7438, CV Loss: 1.0466\n",
      "Epoch [136/200], Train Loss: 0.7438, CV Loss: 1.0476\n",
      "Epoch [137/200], Train Loss: 0.7438, CV Loss: 1.0485\n",
      "Epoch [138/200], Train Loss: 0.7438, CV Loss: 1.0491\n",
      "Epoch [139/200], Train Loss: 0.7438, CV Loss: 1.0494\n",
      "Epoch [140/200], Train Loss: 0.7438, CV Loss: 1.0494\n",
      "Epoch [141/200], Train Loss: 0.7438, CV Loss: 1.0495\n",
      "Epoch [142/200], Train Loss: 0.7438, CV Loss: 1.0492\n",
      "Epoch [143/200], Train Loss: 0.7438, CV Loss: 1.0492\n",
      "Epoch [144/200], Train Loss: 0.7438, CV Loss: 1.0498\n",
      "Epoch [145/200], Train Loss: 0.7438, CV Loss: 1.0496\n",
      "Epoch [146/200], Train Loss: 0.7438, CV Loss: 1.0495\n",
      "Epoch [147/200], Train Loss: 0.7438, CV Loss: 1.0498\n",
      "Epoch [148/200], Train Loss: 0.7438, CV Loss: 1.0492\n",
      "Epoch [149/200], Train Loss: 0.7437, CV Loss: 1.0498\n",
      "Epoch [150/200], Train Loss: 0.7437, CV Loss: 1.0491\n",
      "Epoch [151/200], Train Loss: 0.7437, CV Loss: 1.0488\n",
      "Epoch [152/200], Train Loss: 0.7437, CV Loss: 1.0489\n",
      "Epoch [153/200], Train Loss: 0.7437, CV Loss: 1.0491\n",
      "Epoch [154/200], Train Loss: 0.7437, CV Loss: 1.0487\n",
      "Epoch [155/200], Train Loss: 0.7437, CV Loss: 1.0481\n",
      "Epoch [156/200], Train Loss: 0.7437, CV Loss: 1.0484\n",
      "Epoch [157/200], Train Loss: 0.7437, CV Loss: 1.0483\n",
      "Epoch [158/200], Train Loss: 0.7437, CV Loss: 1.0481\n",
      "Epoch [159/200], Train Loss: 0.7437, CV Loss: 1.0483\n",
      "Epoch [160/200], Train Loss: 0.7437, CV Loss: 1.0482\n",
      "Epoch [161/200], Train Loss: 0.7437, CV Loss: 1.0478\n",
      "Epoch [162/200], Train Loss: 0.7437, CV Loss: 1.0476\n",
      "Epoch [163/200], Train Loss: 0.7437, CV Loss: 1.0477\n",
      "Epoch [164/200], Train Loss: 0.7437, CV Loss: 1.0476\n",
      "Epoch [165/200], Train Loss: 0.7437, CV Loss: 1.0473\n",
      "Epoch [166/200], Train Loss: 0.7437, CV Loss: 1.0471\n",
      "Epoch [167/200], Train Loss: 0.7437, CV Loss: 1.0470\n",
      "Epoch [168/200], Train Loss: 0.7437, CV Loss: 1.0471\n",
      "Epoch [169/200], Train Loss: 0.7437, CV Loss: 1.0467\n",
      "Epoch [170/200], Train Loss: 0.7437, CV Loss: 1.0469\n",
      "Epoch [171/200], Train Loss: 0.7437, CV Loss: 1.0466\n",
      "Epoch [172/200], Train Loss: 0.7437, CV Loss: 1.0468\n",
      "Epoch [173/200], Train Loss: 0.7437, CV Loss: 1.0464\n",
      "Epoch [174/200], Train Loss: 0.7437, CV Loss: 1.0466\n",
      "Epoch [175/200], Train Loss: 0.7437, CV Loss: 1.0463\n",
      "Epoch [176/200], Train Loss: 0.7437, CV Loss: 1.0462\n",
      "Epoch [177/200], Train Loss: 0.7437, CV Loss: 1.0461\n",
      "Epoch [178/200], Train Loss: 0.7437, CV Loss: 1.0460\n",
      "Epoch [179/200], Train Loss: 0.7437, CV Loss: 1.0460\n",
      "Epoch [180/200], Train Loss: 0.7437, CV Loss: 1.0460\n",
      "Epoch [181/200], Train Loss: 0.7437, CV Loss: 1.0459\n",
      "Epoch [182/200], Train Loss: 0.7437, CV Loss: 1.0458\n",
      "Epoch [183/200], Train Loss: 0.7437, CV Loss: 1.0458\n",
      "Epoch [184/200], Train Loss: 0.7437, CV Loss: 1.0457\n",
      "Epoch [185/200], Train Loss: 0.7437, CV Loss: 1.0457\n",
      "Epoch [186/200], Train Loss: 0.7437, CV Loss: 1.0456\n",
      "Epoch [187/200], Train Loss: 0.7437, CV Loss: 1.0455\n",
      "Epoch [188/200], Train Loss: 0.7437, CV Loss: 1.0455\n",
      "Epoch [189/200], Train Loss: 0.7437, CV Loss: 1.0454\n",
      "Epoch [190/200], Train Loss: 0.7437, CV Loss: 1.0454\n",
      "Epoch [191/200], Train Loss: 0.7437, CV Loss: 1.0453\n",
      "Epoch [192/200], Train Loss: 0.7437, CV Loss: 1.0454\n",
      "Epoch [193/200], Train Loss: 0.7437, CV Loss: 1.0453\n",
      "Epoch [194/200], Train Loss: 0.7437, CV Loss: 1.0452\n",
      "Epoch [195/200], Train Loss: 0.7437, CV Loss: 1.0452\n",
      "Epoch [196/200], Train Loss: 0.7437, CV Loss: 1.0454\n",
      "Epoch [197/200], Train Loss: 0.7437, CV Loss: 1.0453\n",
      "Epoch [198/200], Train Loss: 0.7437, CV Loss: 1.0454\n",
      "Epoch [199/200], Train Loss: 0.7437, CV Loss: 1.0452\n",
      "Epoch [200/200], Train Loss: 0.7437, CV Loss: 1.0454\n"
     ]
    }
   ],
   "source": [
    "# Define the model parameters\n",
    "hidden_size = 10\n",
    "\n",
    "# Instantiate the model\n",
    "input_size = trX.shape[1]\n",
    "num_classes = 4 # 3 movements and static\n",
    "model = FNN(input_size, hidden_size, num_classes)\n",
    "print(model)\n",
    "\n",
    "# Define loss function and optimizer\n",
    "criterion = nn.CrossEntropyLoss()\n",
    "optimizer = torch.optim.Adam(model.parameters())\n",
    "\n",
    "# Lists to store losses\n",
    "train_losses = []\n",
    "te_losses = []\n",
    "\n",
    "# Number of epochs\n",
    "num_epochs = 200\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    model.train()\n",
    "    batch_losses = []\n",
    "    \n",
    "    for batch_x, batch_y in trLoader:\n",
    "        # Forward pass\n",
    "        outputs = model(batch_x)\n",
    "        loss = criterion(outputs, batch_y)\n",
    "        \n",
    "        # Backward pass and optimize\n",
    "        optimizer.zero_grad()\n",
    "        loss.backward()\n",
    "        optimizer.step()\n",
    "        \n",
    "        batch_losses.append(loss.item())\n",
    "    \n",
    "    # Calculate average training loss for this epoch\n",
    "    avg_train_loss = sum(batch_losses) / len(batch_losses)\n",
    "    train_losses.append(avg_train_loss)\n",
    "    \n",
    "    # Evaluate on cross-validation set\n",
    "    model.eval()\n",
    "    te_batch_losses = []\n",
    "    with torch.no_grad():\n",
    "        for te_x, te_y in teLoader:\n",
    "            te_outputs = model(te_x)\n",
    "            te_loss = criterion(te_outputs, te_y)\n",
    "            te_batch_losses.append(te_loss.item())\n",
    "    \n",
    "    avg_te_loss = sum(te_batch_losses) / len(te_batch_losses)\n",
    "    te_losses.append(avg_te_loss)\n",
    "    \n",
    "    print(f'Epoch [{epoch+1}/{num_epochs}], Train Loss: {avg_train_loss:.4f}, CV Loss: {avg_te_loss:.4f}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ebcd24b6-ef37-4aae-96b6-195ec4f38c7b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Accuracy on training set: 100.00%\n",
      "Accuracy on cross-validation set: 69.84%\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA04AAAHACAYAAACVhTgAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABmJ0lEQVR4nO3dd3hUZf7+8fvMZDLpgZBACIQiIChNiqigK1ZExYJrRQTrYkdXd+XnV0VXF9e+NnRdBTvqiqwFC7giTaUrAiJCIEFCJ73PnN8fTzIhlCSEJGcmeb+ua67MnDkz85mTwzB3nmbZtm0LAAAAAHBQLqcLAAAAAIBgR3ACAAAAgBoQnAAAAACgBgQnAAAAAKgBwQkAAAAAakBwAgAAAIAaEJwAAAAAoAYEJwAAAACoQZjTBTQ2v9+vLVu2KDY2VpZlOV0OAAAAAIfYtq3c3FylpKTI5aq+TanZBactW7YoNTXV6TIAAAAABImMjAy1b9++2n2aXXCKjY2VZA5OXFycw9UAAAAAcEpOTo5SU1MDGaE6zS44VXTPi4uLIzgBAAAAqNUQHiaHAAAAAIAaEJwAAAAAoAYEJwAAAACoQbMb4wQAAICmw+fzqbS01OkyEMQ8Ho/cbvdhPw/BCQAAACEpLy9Pmzdvlm3bTpeCIGZZltq3b6+YmJjDeh6CEwAAAEKOz+fT5s2bFRUVpaSkpFrNiobmx7Zt7dixQ5s3b1a3bt0Oq+WJ4AQAAICQU1paKtu2lZSUpMjISKfLQRBLSkrSxo0bVVpaeljBickhAAAAELJoaUJN6uscITgBAAAAQA0ITgAAAEAIGzp0qMaPH1/r/Tdu3CjLsrRixYoGq6kpIjgBAAAAjcCyrGovY8eOrdPzTp8+XX/7299qvX9qaqoyMzPVq1evOr1ebTW1gMbkEAAAAEAjyMzMDFx/7733dP/992vt2rWBbftOclFaWiqPx1Pj8yYkJBxSHW63W8nJyYf0GNDiBAAAADSK5OTkwCU+Pl6WZQVuFxUVqUWLFnr//fc1dOhQRURE6K233tKuXbt0+eWXq3379oqKilLv3r317rvvVnnefbvqderUSX//+991zTXXKDY2Vh06dNC//vWvwP37tgTNmTNHlmXp66+/1sCBAxUVFaXBgwdXCXWS9PDDD6t169aKjY3Vddddp3vuuUfHHHNMnY9HcXGxbrvtNrVu3VoRERE68cQTtXjx4sD9e/bs0ahRowIzJ3br1k1TpkyRJJWUlOiWW25R27ZtFRERoU6dOmnSpEl1rqU2CE4O+u+K33XWM3P18KernS4FAAAgpNm2rYKSMkcu9bkA71//+lfddtttWrNmjYYNG6aioiINGDBAn376qX7++WfdcMMNGj16tH744Ydqn+fJJ5/UwIEDtXz5ct1000268cYb9csvv1T7mHvvvVdPPvmklixZorCwMF1zzTWB+95++2098sgj+sc//qGlS5eqQ4cOmjx58mG917/85S/68MMP9frrr2vZsmXq2rWrhg0bpt27d0uS7rvvPq1evVqff/651qxZo8mTJysxMVGS9Oyzz+rjjz/W+++/r7Vr1+qtt95Sp06dDquemtBVz0G5RWX6ZWuuOiREOV0KAABASCss9eno+7905LVXPzRMUeH187V6/PjxGjlyZJVtd911V+D6rbfeqi+++EIffPCBjjvuuIM+z9lnn62bbrpJkgljTz/9tObMmaMePXoc9DGPPPKITj75ZEnSPffco3POOUdFRUWKiIjQc889p2uvvVZXX321JOn+++/XV199pby8vDq9z/z8fE2ePFlTp07V8OHDJUmvvPKKZs2apVdffVV333230tPT1a9fPw0cOFCSqgSj9PR0devWTSeeeKIsy1LHjh3rVMehoMXJQdFeswBXYanP4UoAAAAQDCpCQgWfz6dHHnlEffr0UatWrRQTE6OvvvpK6enp1T5Pnz59AtcrugRu37691o9p27atJAUes3btWg0aNKjK/vvePhTr169XaWmphgwZEtjm8Xg0aNAgrVmzRpJ04403atq0aTrmmGP0l7/8RQsXLgzsO3bsWK1YsULdu3fXbbfdpq+++qrOtdQWLU4OivSYw59fXOZwJQAAAKEt0uPW6oeGOfba9SU6OrrK7SeffFJPP/20nnnmGfXu3VvR0dEaP368SkpKqn2efSeVsCxLfr+/1o+pWDR278fsu5Ds4XRRrHjsgZ6zYtvw4cO1adMmffbZZ5o9e7ZOO+003XzzzXriiSfUv39/paWl6fPPP9fs2bN1ySWX6PTTT9d//vOfOtdUE1qcHBQVbv6RFZTQ4gQAAHA4LMtSVHiYI5d9v/zXp3nz5un888/XlVdeqb59++qII47QunXrGuz1DqZ79+5atGhRlW1Lliyp8/N17dpV4eHhmj9/fmBbaWmplixZoqOOOiqwLSkpSWPHjtVbb72lZ555psokF3Fxcbr00kv1yiuv6L333tOHH34YGB/VEGhxclBFcKKrHgAAAA6ka9eu+vDDD7Vw4UK1bNlSTz31lLZu3VolXDSGW2+9Vddff70GDhyowYMH67333tNPP/2kI444osbH7js7nyQdffTRuvHGG3X33XcrISFBHTp00GOPPaaCggJde+21ksw4qgEDBqhnz54qLi7Wp59+GnjfTz/9tNq2batjjjlGLpdLH3zwgZKTk9WiRYt6fd97Izg5qGIQIS1OAAAAOJD77rtPaWlpGjZsmKKionTDDTfoggsuUHZ2dqPWMWrUKG3YsEF33XWXioqKdMkll2js2LH7tUIdyGWXXbbftrS0ND366KPy+/0aPXq0cnNzNXDgQH355Zdq2bKlJCk8PFwTJkzQxo0bFRkZqZNOOknTpk2TJMXExOgf//iH1q1bJ7fbrWOPPVYzZ86Uy9VwHeosuz7nTwwBOTk5io+PV3Z2tuLi4hytZePOfA19Yo6iw91a9dBZjtYCAAAQSoqKipSWlqbOnTsrIiLC6XKapTPOOEPJycl68803nS6lWtWdK4eSDWhxclBgjFOpr8pAOAAAACCYFBQU6KWXXtKwYcPkdrv17rvvavbs2Zo1a5bTpTUagpODIsuDk21LxWV+RdTjjCwAAABAfbEsSzNnztTDDz+s4uJide/eXR9++KFOP/10p0trNI7Oqjd37lyNGDFCKSkpsixLM2bMqHb/+fPna8iQIWrVqpUiIyPVo0cPPf30041TbAPYe6E0xjkBAAAgWEVGRmr27NnavXu38vPztWzZsv0W6m3qHG1xys/PV9++fXX11VfroosuqnH/6Oho3XLLLerTp4+io6M1f/58/elPf1J0dLRuuOGGRqi4frldlrxhLhWX+ZVfXKaE6HCnSwIAAABwAI4Gp+HDh2v48OG13r9fv37q169f4HanTp00ffp0zZs3LySDk2TGORWX+ZmSHAAAAAhiIb0A7vLly7Vw4UKdfPLJB92nuLhYOTk5VS7BhCnJAQAAgOAXksGpffv28nq9GjhwoG6++WZdd911B9130qRJio+PD1xSU1MbsdKaVUwQUVBS5nAlAAAAAA4mJIPTvHnztGTJEr300kt65pln9O677x503wkTJig7OztwycjIaMRKaxZdHpwKaXECAAAAglZITkfeuXNnSVLv3r21bds2TZw4UZdffvkB9/V6vfJ6vY1Z3iGpaHHKJzgBAAAAQSskW5z2Ztu2iouLnS6jzirGOBXSVQ8AAAD1aOrUqWrRooXTZTQZjganvLw8rVixQitWrJAkpaWlacWKFUpPT5dkutldddVVgf1feOEFffLJJ1q3bp3WrVunKVOm6IknntCVV17pRPn1onKMEy1OAAAATZllWdVexo4dW+fn7tSpk5555pkq2y699FL9+uuvh1d0LTSXgOZoV70lS5bolFNOCdy+8847JUljxozR1KlTlZmZGQhRkuT3+zVhwgSlpaUpLCxMXbp00aOPPqo//elPjV57fYkmOAEAADQLmZmZgevvvfee7r//fq1duzawLTIysl5fLzIyst6fszlztMVp6NChsm17v8vUqVMlmfQ6Z86cwP633nqrfv75Z+Xn5ys7O1vLli3TjTfeKJcrdHscVk5HTlc9AACApiw5OTlwiY+Pl2VZVbbNnTtXAwYMUEREhI444gg9+OCDKiur/I44ceJEdejQQV6vVykpKbrtttskme/UmzZt0h133BFovZL2bwmaOHGijjnmGL355pvq1KmT4uPjddlllyk3NzewT25urkaNGqXo6Gi1bdtWTz/9tIYOHarx48fX+X2np6fr/PPPV0xMjOLi4nTJJZdo27Ztgft//PFHnXLKKYqNjVVcXJwGDBigJUuWSJI2bdqkESNGqGXLloqOjlbPnj01c+bMOtdyOEJycoimhK56AAAA9cC2pdICZ17bEyWVh5W6+vLLL3XllVfq2Wef1UknnaT169frhhtukCQ98MAD+s9//qOnn35a06ZNU8+ePbV161b9+OOPkqTp06erb9++uuGGG3T99ddX+zrr16/XjBkz9Omnn2rPnj265JJL9Oijj+qRRx6RZHqALViwQB9//LHatGmj+++/X8uWLdMxxxxTp/dl27YuuOACRUdH69tvv1VZWZluuukmXXrppYEGklGjRqlfv36aPHmy3G63VqxYIY/HI0m6+eabVVJSorlz5yo6OlqrV69WTExMnWo5XAQnh0V5mI4cAADgsJUWSH9Pcea1/98WKTz6sJ7ikUce0T333KMxY8ZIko444gj97W9/01/+8hc98MADSk9PV3Jysk4//XR5PB516NBBgwYNkiQlJCTI7XYrNjZWycnJ1b6O3+/X1KlTFRsbK0kaPXq0vv76az3yyCPKzc3V66+/rnfeeUennXaaJGnKlClKSan7cZ09e7Z++uknpaWlBdZTffPNN9WzZ08tXrxYxx57rNLT03X33XerR48ekqRu3boFHp+enq6LLrpIvXv3DhwXp4RuH7cmghYnAAAALF26VA899JBiYmICl+uvv16ZmZkqKCjQxRdfrMLCQh1xxBG6/vrr9dFHH1XpxldbnTp1CoQmSWrbtq22b98uSdqwYYNKS0sDgUyS4uPj1b179zq/rzVr1ig1NTUQmiTp6KOPVosWLbRmzRpJppXruuuu0+mnn65HH31U69evD+x722236eGHH9aQIUP0wAMP6KeffqpzLYeLFieHRXsZ4wQAAHDYPFGm5cep1z5Mfr9fDz74oEaOHLnffREREUpNTdXatWs1a9YszZ49WzfddJMef/xxffvtt4FubbUqdZ99LcuS3++XZLrVVWzbW8X2urBte7/n23f7xIkTdcUVV+izzz7T559/rgceeEDTpk3ThRdeqOuuu07Dhg3TZ599pq+++kqTJk3Sk08+qVtvvbXONdUVLU4Oi6LFCQAA4PBZluku58TlMMc3SVL//v21du1ade3adb9LxURokZGROu+88/Tss89qzpw5+u6777Ry5UpJUnh4uHy+w/s+2aVLF3k8Hi1atCiwLScnR+vWravzcx599NFKT09XRkZGYNvq1auVnZ2to446KrDtyCOP1B133KGvvvpKI0eO1JQpUwL3paamaty4cZo+fbr+/Oc/65VXXqlzPYeDFieHRXoITgAAAM3d/fffr3PPPVepqam6+OKL5XK59NNPP2nlypV6+OGHNXXqVPl8Ph133HGKiorSm2++qcjISHXs2FGS6YI3d+5cXXbZZfJ6vUpMTDzkGmJjYzVmzBjdfffdSkhIUOvWrfXAAw/I5XIdsNVobz6fL7A2a4Xw8HCdfvrp6tOnj0aNGqVnnnkmMDnEySefrIEDB6qwsFB33323/vjHP6pz587avHmzFi9erIsuukiSNH78eA0fPlxHHnmk9uzZo//9739VAldjIjg5rGI6ciaHAAAAaL6GDRumTz/9VA899JAee+wxeTwe9ejRQ9ddd50kqUWLFnr00Ud15513yufzqXfv3vrkk0/UqlUrSdJDDz2k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ghOaPMuyNGlkbw3tnqSiUr/GvrZIq7YwZTkAAABqz/Hg9OKLL6pz586KiIjQgAEDNG/evGr3f/vtt9W3b19FRUWpbdu2uvrqq7Vr165GqhahyuN26YUr+mtAx5bKKSrT6FcXad02FssFAABA7TganN577z2NHz9e9957r5YvX66TTjpJw4cPV3p6+gH3nz9/vq666ipde+21WrVqlT744AMtXrxY1113XSNXjlAU7Q3TlKuPVe928dqdX6JR//5BG3fmO10WAAAAQoCjwempp57Stddeq+uuu05HHXWUnnnmGaWmpmry5MkH3P/7779Xp06ddNttt6lz58468cQT9ac//UlLlixp5MoRquIiPHrjmkHqkRyr7bnFuuKV77WcMU8AAACogWPBqaSkREuXLtWZZ55ZZfuZZ56phQsXHvAxgwcP1ubNmzVz5kzZtq1t27bpP//5j84555yDvk5xcbFycnKqXNC8tYwO15vXHqcuSdHakl2kiyYv1KOf/6KiUp/TpQEAACBIORacdu7cKZ/PpzZt2lTZ3qZNG23duvWAjxk8eLDefvttXXrppQoPD1dycrJatGih55577qCvM2nSJMXHxwcuqamp9fo+EJqSYr2afuMQXdivnfy29NK36zXiufn6aXOW06UBAAAgCDk+OYRlWVVu27a937YKq1ev1m233ab7779fS5cu1RdffKG0tDSNGzfuoM8/YcIEZWdnBy4ZGRn1Wj9CV3yUR09feoxeHj1AiTHhWrc9Txe+uFBPfLlWxWW0PgEAAKBSmFMvnJiYKLfbvV/r0vbt2/drhaowadIkDRkyRHfffbckqU+fPoqOjtZJJ52khx9+WG3btt3vMV6vV16vt/7fAJqMYT2TdWynBD3w8Sp98uMWPf/Nb5q9ZpueuLiverWLd7o8AAAABAHHWpzCw8M1YMAAzZo1q8r2WbNmafDgwQd8TEFBgVyuqiW73W5JpqUKqKuE6HA9d3k/vTiqvxKiw/XL1lxd8MICPfDfn7Urr9jp8gAAAOAwR7vq3Xnnnfr3v/+t1157TWvWrNEdd9yh9PT0QNe7CRMm6KqrrgrsP2LECE2fPl2TJ0/Whg0btGDBAt12220aNGiQUlJSnHobaELO7t1WX93xB53dO1llfluvf7dJQx+foxfn/MbkEQAAAM2YY131JOnSSy/Vrl279NBDDykz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u5kU2p/FdNVrJImJiXK73ful5e3bt++XvFF7t956qz7++GN98803at++fbX7tm3bVh07dtS6desaqbqmIzo6Wr1799a6desCs+txLtePTZs2afbs2bruuuuq3Y/zt+5qc84mJyerpKREe/bsOeg+qFlpaakuueQSpaWladasWVVamw6kf//+8ng8nNd1sO9nAudw/Zk3b57Wrl1b4+eyxDl8IAf7btYUPosJTo0kPDxcAwYM2K8pd9asWRo8eLBDVYUu27Z1yy23aPr06frf//6nzp071/iYXbt2KSMjQ23btm2ECpuW4uJirVmzRm3btg10Udj7XC4pKdG3337LuVwHU6ZMUevWrXXOOedUux/nb93V5pwdMGCAPB5PlX0yMzP1888/c17XUkVoWrdunWbPnq1WrVrV+JhVq1aptLSU87oO9v1M4ByuP6+++qoGDBigvn371rgv53Clmr6bNYnPYocmpWiWpk2bZns8HvvVV1+1V69ebY8fP96Ojo62N27c6HRpIefGG2+04+Pj7Tlz5tiZmZmBS0FBgW3btp2bm2v/+c9/thcuXGinpaXZ33zzjX3CCSfY7dq1s3NychyuPvj9+c9/tufMmWNv2LDB/v777+1zzz3Xjo2NDZyrjz76qB0fH29Pnz7dXrlypX355Zfbbdu25dgeIp/PZ3fo0MH+61//WmU75++hy83NtZcvX24vX77clmQ/9dRT9vLlywMzutXmnB03bpzdvn17e/bs2fayZcvsU0891e7bt69dVlbm1NsKKtUd49LSUvu8886z27dvb69YsaLK53JxcbFt27b922+/2Q8++KC9ePFiOy0tzf7ss8/sHj162P369eMY29Uf39p+JnAOV6+mzwnbtu3s7Gw7KirKnjx58n6P5xyuXk3fzWw79D+LCU6N7IUXXrA7duxoh4eH2/37968yfTZqT9IBL1OmTLFt27YLCgrsM888005KSrI9Ho/doUMHe8yYMXZ6erqzhYeISy+91G7btq3t8XjslJQUe+TIkfaqVasC9/v9fvuBBx6wk5OTba/Xa//hD3+wV65c6WDFoenLL7+0Jdlr166tsp3z99B98803B/xMGDNmjG3btTtnCwsL7VtuucVOSEiwIyMj7XPPPZdjvpfqjnFaWtpBP5e/+eYb27ZtOz093f7DH/5gJyQk2OHh4XaXLl3s2267zd61a5ezbyxIVHd8a/uZwDlcvZo+J2zbtl9++WU7MjLSzsrK2u/xnMPVq+m7mW2H/mexZdu23UCNWQAAAADQJDDGCQAAAABqQHACAAAAgBoQnAAAAACgBgQnAAAAAKgBwQkAAAAAakBwAgAAAIAaEJwAAAAAoAYEJwAADoFlWZoxY4bTZQAAGhnBCQAQMsaOHSvLsva7nHXWWU6XBgBo4sKcLgAAgENx1llnacqUKVW2eb1eh6oBADQXtDgBAEKK1+tVcnJylUvLli0lmW50kydP1vDhwxUZGanOnTvrgw8+qPL4lStX6tRTT1VkZKRatWqlG264QXl5eVX2ee2119SzZ095vV61bdtWt9xyS5X7d+7cqQsvvFBRUVHq1q2bPv7444Z90wAAxxGcAABNyn333aeLLrpIP/74o6688kpdfvnlWrNmjSSpoKBAZ511llq2bKnFixfrgw8+0OzZs6sEo8mTJ+vmm2/WDTfcoJUrV+rjjz9W165dq7zGgw8+qEsuuUQ//fSTzj77bI0aNUq7d+9u1PcJAGhclm3bttNFAABQG2PHjtVbb72liIiIKtv/+te/6r777pNlWRo3bpwmT54cuO/4449X//799eKLL+qVV17RX//6V2VkZCg6OlqSNHPmTI0YMUJbtmxRmzZt1K5dO1199dV6+OGHD1iDZVn6v//7P/3tb3+TJOXn5ys2NlYzZ85krBUANGGMcQIAhJRTTjmlSjCSpISEhMD1E044ocp9J5xwglasWCFJWrNmjfr27RsITZI0ZMgQ+f1+rV27VpZlacuWLTrttNOqraFPnz6B69HR0YqNjdX27dvr+pYAACGA4AQACCnR0dH7dZ2riWVZkiTbtgPXD7RPZGRkrZ7P4/Hs91i/339INQEAQgtjnAAATcr333+/3+0ePXpIko4++mitWLFC+fn5gfsXLFggl8ulI488UrGxserUqZO+/vrrRq0ZABD8aHECAISU4uJibd26tcq2sLAwJSYmSpI++OADDRw4UCeeeKLefvttLVq0SK+++qokadSoUXrggQc0ZswYTZw4UTt27NCtt96q0aNHq02bNpKkiRMnaty4cWrdurWGDx+u3NxcLViwQLfeemvjvlEAQFAhOAEAQsoXX3yhtm3bVtnWvXt3/fLLL5LMjHfTpk3TTTfdpOTkZL399ts6+uijJUlRUVH68ssvdfvtt+vYY49VVFSULrroIj311FOB5xozZoyKior09NNP66677lJiYqL++Mc/Nt4bBAAEJWbVAwA0GZZl6aOPPtIFF1zgdCkAgCaGMU4AAAAAUAOCEwAAAADUgDFOAIAmg97nAICGQosTAAAAANSA4AQAAAAANSA4AQAAAEANCE4AAAAAUAOCEwAAAADUgOAEAAAAADUgOAEAAABADQhOAAAAAFADghMAAAAA1OD/A3yJ+YORULtfAAAAAElFTkSuQmCC",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "# Calculate and print accuracies for training and cross-validation sets\n",
    "model.eval()\n",
    "with torch.no_grad():\n",
    "    # Training set accuracy\n",
    "    tr_correct = 0\n",
    "    tr_total = 0\n",
    "    for images, labels in trLoader:\n",
    "        outputs = model(images)\n",
    "        _, predicted = torch.max(outputs, 1)\n",
    "        _, true_labels = torch.max(labels, 1)\n",
    "        tr_total += labels.size(0)\n",
    "        tr_correct += (predicted == true_labels).sum().item()\n",
    "    \n",
    "    tr_accuracy = 100 * tr_correct / tr_total\n",
    "    \n",
    "    # test set accuracy\n",
    "    te_correct = 0\n",
    "    te_total = 0\n",
    "    for images, labels in teLoader:\n",
    "        outputs = model(images)\n",
    "        _, predicted = torch.max(outputs, 1)\n",
    "        _, true_labels = torch.max(labels, 1)\n",
    "        te_total += labels.size(0)\n",
    "        te_correct += (predicted == true_labels).sum().item()\n",
    "    \n",
    "    te_accuracy = 100 * te_correct / te_total\n",
    "\n",
    "print(f'Accuracy on training set: {tr_accuracy:.2f}%')\n",
    "print(f'Accuracy on cross-validation set: {te_accuracy:.2f}%')\n",
    "\n",
    "# Plot training and cross-validation losses\n",
    "plt.figure(figsize=(10, 5))\n",
    "plt.plot(range(1, num_epochs+1), train_losses, label='Training Loss')\n",
    "plt.plot(range(1, num_epochs+1), te_losses, label='Testing Loss')\n",
    "plt.xlabel('Epoch')\n",
    "plt.ylabel('Loss')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "20878610-ff78-4a3c-bb9e-cf2ece360264",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
